package com.haohan.jingyuan.gateway.filter;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.haohan.jingyuan.config.properties.GatewayProperties;
import com.haohan.jingyuan.config.properties.JwtProperties;
import com.haohan.jingyuan.gateway.context.UserContext;
import com.haohan.jingyuan.gateway.dto.GwResponse;
import com.haohan.jingyuan.gateway.holder.UserContextHolder;
import com.haohan.jingyuan.user.entity.AdminInfo;
import com.haohan.jingyuan.user.entity.UserInfo;
import com.haohan.jingyuan.util.AesUtil;
import com.haohan.jingyuan.util.CacheUtil;
import com.haohan.jingyuan.wrapper.GwHttpServletRequestWrapper;
import com.haohan.jingyuan.wrapper.GwHttpServletResponseWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @ClassName GatewayFilter
 * @Description 统一网关过滤器，整合AES解密/加密、JWT鉴权、跨域处理与ThreadLocal清理
 * @Author hexijun
 * @Time 2025/10/28 15:01
 * @Version 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class GatewayFilter implements Filter {

    /**
     * 网关配置
     */
    private final GatewayProperties gatewayProperties;
    /**
     * JWT配置
     */
    private final JwtProperties jwtProperties;
    /**
     * 缓存工具
     */
    private final CacheUtil cacheUtil;

    /**
     * 过滤器主流程
     *
     * @param servletRequest  请求
     * @param servletResponse 响应
     * @param filterChain     过滤器链
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 处理跨域
        handleCors(request, response);

        // 预检请求直接放行
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        // AES解密并包装请求
        GwHttpServletRequestWrapper requestWrapper = decryptRequest(request);
        GwHttpServletResponseWrapper responseWrapper = new GwHttpServletResponseWrapper(response);

        try {
            // JWT 鉴权（含白名单和是否开启开关）
            if (needAuth(requestWrapper)) {
                if (!bindContextWithAuth(requestWrapper, response)) {
                    return;
                }
            } else {
                bindContextWithoutAuth(requestWrapper);
            }

            // 放行业务处理
            filterChain.doFilter(requestWrapper, responseWrapper);

            // 对响应进行AES加密（如开启）
            encryptResponseIfNeeded(responseWrapper, response);
        } finally {
            // 清理ThreadLocal，防止内存泄漏
            UserContextHolder.clear();
        }
    }

    /**
     * 处理跨域请求
     *
     * @param request  请求
     * @param response 响应
     */
    private void handleCors(HttpServletRequest request, HttpServletResponse response) {
        // 设置跨域响应头
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Authorization, X-Userid, X-Username");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Credentials", "false");
    }

    /**
     * 解密请求体并包装请求
     *
     * @param request 原始请求
     * @return 包装后的请求
     */
    private GwHttpServletRequestWrapper decryptRequest(HttpServletRequest request) {
        try {
            String uri = request.getRequestURI();
            if (GwWhiteList.aesList.contains(uri)) {
                return new GwHttpServletRequestWrapper(request, null);
            } else {
                String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
                if (!StringUtils.hasText(body)) {
                    return new GwHttpServletRequestWrapper(request, null);
                }
                JSONObject json = JSONUtil.parseObj(body);
                if (!json.containsKey("requestData")) {
                    return new GwHttpServletRequestWrapper(request, body);
                }
                Object requestDataNode = json.get("requestData");
                String requestDataJson = JSONUtil.toJsonStr(requestDataNode);
                if (gatewayProperties.isAesEnabled()) {
                    requestDataJson = AesUtil.decrypt(requestDataJson, gatewayProperties.getAesKey());
                    json.putOpt("requestData", JSONUtil.parseObj(requestDataJson));
                }
                String decrypted = JSONUtil.toJsonStr(json);
                return new GwHttpServletRequestWrapper(request, decrypted);
            }
        } catch (Exception ex) {
            log.error("网关解密请求体失败, uri={}", request.getRequestURI(), ex);
            return new GwHttpServletRequestWrapper(request, null);
        }
    }

    /**
     * 加密响应体并写回客户端
     *
     * @param responseWrapper 包装响应
     * @param rawResponse     原始响应
     * @throws IOException IO异常
     */
    private void encryptResponseIfNeeded(GwHttpServletResponseWrapper responseWrapper, HttpServletResponse rawResponse)
            throws IOException {
        try {
            String content = responseWrapper.getResponseContent();
            if (!StringUtils.hasText(content)) {
                return;
            }
            JSONObject json = JSONUtil.parseObj(content);
            Object data = json.get("data");
            if (data == null) {
                writeUtf8(rawResponse, JSONUtil.toJsonStr(json));
                return;
            }
            String payload = JSONUtil.toJsonStr(data);
            if (gatewayProperties.isAesEnabled()) {
                payload = AesUtil.encrypt(payload, gatewayProperties.getAesKey());
                json.putOpt("data", payload);
            }
            writeUtf8(rawResponse, JSONUtil.toJsonStr(json));
        } catch (Exception ex) {
            log.error("网关加密响应体失败", ex);
            // 失败时尽量透传原始响应内容
            writeUtf8(rawResponse, responseWrapper.getResponseContent());
        }
    }

    /**
     * 写入UTF-8响应
     *
     * @param response 响应
     * @param body     文本
     * @throws IOException IO异常
     */
    private void writeUtf8(HttpServletResponse response, String body) throws IOException {
        byte[] bytes = body == null ? new byte[0] : body.getBytes(StandardCharsets.UTF_8);
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentLength(bytes.length);
        response.getOutputStream().write(bytes);
        response.getOutputStream().flush();
    }

    /**
     * 绑定上下文但不做鉴权（白名单或未开启鉴权）
     *
     * @param request 请求
     */
    private void bindContextWithoutAuth(GwHttpServletRequestWrapper request) {
        String jwt = getJwtFromRequest(request, jwtProperties.getAuthorization());
        String userid = getJwtFromRequest(request, jwtProperties.getUserid());
        String username = getJwtFromRequest(request, jwtProperties.getUsername());
        UserContext context = new UserContext();
        context.setToken(jwt);
        context.setUserId(userid);
        context.setUserName(username);
        UserContextHolder.setUserContext(context);
    }

    /**
     * 执行JWT鉴权并绑定上下文
     *
     * @param request  请求
     * @param response 响应
     * @return 是否通过
     * @throws IOException IO异常
     */
    private boolean bindContextWithAuth(GwHttpServletRequestWrapper request, HttpServletResponse response) throws IOException {
        String jwt = getJwtFromRequest(request, jwtProperties.getAuthorization());
        String userid = getJwtFromRequest(request, jwtProperties.getUserid());
        String username = getJwtFromRequest(request, jwtProperties.getUsername());

        UserContext context = new UserContext();
        context.setToken(jwt);
        context.setUserId(userid);
        context.setUserName(username);
        UserContextHolder.setUserContext(context);

        if (!StringUtils.hasText(jwt) || !StringUtils.hasText(userid) || !StringUtils.hasText(username)) {
            authCheckFail(request, response);
            return false;
        }

        String[] jwts = jwt.split(" ");
        if (jwts.length != 2 || !jwts[0].equals(userid)) {
            authCheckFail(request, response);
            return false;
        }

        AdminInfo adminInfo = cacheUtil.get(jwt, AdminInfo.class);
        UserInfo userInfo = cacheUtil.get(jwt, UserInfo.class);
        if (adminInfo == null && userInfo == null) {
            authCheckFail(request, response);
            return false;
        }
        if (adminInfo != null && (!userid.equals(adminInfo.getUserId()) || !username.equals(adminInfo.getUserName()))) {
            authCheckFail(request, response);
            return false;
        }
        if (userInfo != null && (!userid.equals(userInfo.getUserId()) || !username.equals(userInfo.getUserName()))) {
            authCheckFail(request, response);
            return false;
        }
        return true;
    }

    /**
     * 获取请求头中的JWT相关字段
     *
     * @param request 请求
     * @param header  头名
     * @return 值
     */
    private String getJwtFromRequest(HttpServletRequest request, String header) {
        return request.getHeader(header);
    }

    /**
     * 判断是否需要鉴权
     *
     * @param request 请求
     * @return 是否需要鉴权
     */
    private boolean needAuth(GwHttpServletRequestWrapper request) {
        // 预检请求，无需auth鉴权
        String method = request.getMethod();
        if ("OPTIONS".equalsIgnoreCase(method)) {
            return false;
        }
        // 白名单uri，无需auth鉴权
        String uri = request.getRequestURI();
        if (GwWhiteList.jwtList.contains(uri)) {
            return false;
        }
        // 根据auth开关决定是否需要auth鉴权
        return gatewayProperties.isAuthEnabled();
    }

    /**
     * 鉴权失败响应
     *
     * @param request  请求
     * @param response 响应
     * @throws IOException IO异常
     */
    private void authCheckFail(GwHttpServletRequestWrapper request, HttpServletResponse response) throws IOException {
        GwResponse<Void> gw = new GwResponse<>();
        gw.setCode(HttpServletResponse.SC_UNAUTHORIZED);
        gw.setMessage("鉴权失败");
        gw.setPath(request.getRequestURI());
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        writeUtf8(response, JSONUtil.toJsonStr(gw));
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化方法，无需实现
    }

    @Override
    public void destroy() {
        // 销毁方法，无需实现
    }
}
